﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Creventive.Wpf.ShellFactory.Utility
{
    /// <summary>
    /// Base class for events which is released earlier than conventional events
    /// </summary>
    public abstract class WeakEventContainerBase
    {
        private List<WeakEventHandler> handlers = new List<WeakEventHandler>();

        /// <summary>
        /// Gets a value indicating whether this event is connected to listeners.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is connected; otherwise, <c>false</c>.
        /// </value>
        public bool IsConnected
        {
            get { return this.handlers.Count(h => !h.IsDead()) > 0; }
        }

        /// <summary>
        /// Adds the listener.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="method">The method.</param>
        protected virtual void AddListenerCore(object target, MethodInfo method)
        {
            lock (this.handlers)
            {
                WeakEventHandler oldHandler = this.handlers.SingleOrDefault(h => h.IsEquivalentTo(target, method));
                if (oldHandler != null)
                    return;
                this.handlers.Add(new WeakEventHandler(target, method));
            }
        }

        /// <summary>
        /// Removes the listener.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="method">The method.</param>
        protected virtual void RemoveListenerCore(object target, MethodInfo method)
        {
            lock (this.handlers)
            {
                WeakEventHandler oldHandler = this.handlers.SingleOrDefault(h => h.IsEquivalentTo(target, method));
                if (oldHandler != null)
                    this.handlers.Remove(oldHandler);
            }
        }

        /// <summary>
        /// Invokes the event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="arguments">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void InvokeCore(object sender, EventArgs arguments)
        {
            KeyValuePair<WeakEventHandler, object>[] eventHandlers;
            lock (this.handlers)
            {
                // Koplingen inn i en KeyValuePair gjøres for å forhindre race condition mellom en weak reference og garbage collection.
                eventHandlers = this.handlers.Select(h => new KeyValuePair<WeakEventHandler, object>(h, h.Target)).Where(p => !p.Key.IsDead()).ToArray();
                this.FlushCore();
            }
            foreach (WeakEventHandler handler in eventHandlers.Select(e => e.Key))
                handler.Invoke(sender, arguments);
        }

        /// <summary>
        /// Flushes this container, removing any "dead" listeners.
        /// </summary>
        public void Flush()
        {
            lock (this.handlers)
                this.FlushCore();
        }

        private void FlushCore()
        {
            if (this.handlers.Count > 0)
                this.handlers.RemoveAll(h => h.IsDead());
        }

        #region Nested type: WeakEventHandler

        private class WeakEventHandler
        {
            private MethodInfo method;
            private WeakReference target;

            public WeakEventHandler(object target, MethodInfo method)
            {
                this.method = method;
                if (target != null)
                    this.target = new WeakReference(target);
            }

            private bool IsStatic
            {
                get { return this.target == null; }
            }

            private MethodInfo Method
            {
                get { return this.method; }
            }

            public object Target
            {
                get { return this.target.Target; }
            }

            public bool IsDead()
            {
                return !this.IsStatic && this.Target == null;
            }

            public void Invoke(object sender, EventArgs e)
            {
                object target = this.Target;
                if (target == null)
                    return;
                this.method.Invoke(target, new[]
                                           {
                                               sender, e
                                           });
            }

            public bool IsEquivalentTo(object target, MethodInfo method)
            {
                if (target == null && !this.IsStatic)
                    return false;
                return (this.Target == target && this.Method == method);
            }
        }

        #endregion
    }
}